#include "oled.h"

#include <stdio.h>

#include "i2c.h"
#include "string.h"
#include "stm32l4xx_hal.h"
#include <math.h>

extern u8g2_t u8g2;

/* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
static uint8_t buffer[128];
static uint8_t buf_idx;
uint8_t *data;


/*
 * @brief  u8g2 硬件 I2C 通信函数
 * @param  u8x8: u8g2 结构体指针
 * @param  msg: 消息类型
 * @param  arg_int: 参数
 * @param  arg_ptr: 参数指针
 * @retval 0: 失败 1: 成功
 */
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
  switch (msg) {
    case U8X8_MSG_BYTE_INIT:
      // I2C初始化可选，如果在外部已初始化则无需重复
      break;

    case U8X8_MSG_BYTE_START_TRANSFER:
      buf_idx = 0;
      break;

    case U8X8_MSG_BYTE_SEND:
      data = (uint8_t *)arg_ptr;
      memcpy(buffer + buf_idx, data, arg_int);
      buf_idx += arg_int;
      break;

    case U8X8_MSG_BYTE_END_TRANSFER:
      HAL_I2C_Master_Transmit_DMA(&hi2c1, OLED_ADDRESS, buffer, buf_idx);
      while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY) {}
      break;

    case U8X8_MSG_BYTE_SET_DC:
      // DC 信号通常用于 SPI，与 I2C 无关，可忽略
      break;

    default:
      return 0;
  }
  return 1;
}

/**
 * @brief  u8g2 延时函数
 * @param  u8x8: u8g2 结构体指针
 * @param  msg: 消息类型
 * @param  arg_int: 参数
 * @param  arg_ptr: 参数指针
 * @retval None
 */
uint8_t stm32_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
  switch (msg) {
    case U8X8_MSG_DELAY_100NANO:
      HAL_Delay(1);
      break;

    case U8X8_MSG_DELAY_10MICRO:
      for (uint16_t n = 0; n < 320; n++) {
        HAL_Delay(1);
      }
      break;

    case U8X8_MSG_DELAY_MILLI:
      HAL_Delay(arg_int);
      break;

    case U8X8_MSG_DELAY_I2C:
      for (uint16_t n = 0; n < 160; n++) {
        HAL_Delay(1);
      }
      break;

    case U8X8_MSG_GPIO_I2C_CLOCK:
    case U8X8_MSG_GPIO_I2C_DATA:
      // I2C 硬件模式下通常无需处理这些 GPIO 信号
      break;

    case U8X8_MSG_GPIO_MENU_SELECT:
    case U8X8_MSG_GPIO_MENU_NEXT:
    case U8X8_MSG_GPIO_MENU_PREV:
    case U8X8_MSG_GPIO_MENU_HOME:
      u8x8_SetGPIOResult(u8x8, 0);
      break;

    default:
      u8x8_SetGPIOResult(u8x8, 1);
      break;
  }
  return 1;
}

/**
 * @brief 初始化 OLED 显示屏（SSD1306，I2C 接口，128x64 像素）
 * @note
 *       - 使用 u8g2 库初始化 SSD1306 控制器的 OLED 屏幕
 *       - 通过 STM32 的硬件 I2C 外设和 GPIO 延时函数与 u8g2 库对接
 *       - 屏幕分辨率为 128x64 像素，初始化后关闭省电模式以点亮屏幕
 *       - 该函数只需在程序启动时（例如 main.c 或 StartDefaultTask）调用一次
 */
void oled_init(void)
{
  /**
     * 配置 u8g2 库以驱动 SSD1306 I2C 屏幕
     * 参数说明：
     *   - &u8g2: u8g2 实例的指针，用于存储屏幕配置
     *   - U8G2_R0: 屏幕方向（R0=不旋转，R1=90°，R2=180°，R3=270°）
     *   - u8x8_byte_hw_i2c: u8g2 提供的硬件 I2C 接口函数，适配 STM32CubeMX 的 hi2c1
     *   - u8x8_stm32_gpio_and_delay: u8g2 提供的 STM32 GPIO 和延时函数适配
     */
  u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_hw_i2c, stm32_gpio_and_delay);

  u8g2_InitDisplay(&u8g2);
  u8g2_SetPowerSave(&u8g2, 0);  // 关闭省电模式，开启显示
}


/**
 * @brief 显示主屏幕，包含电池电量、温度、湿度、EC 数据
 * @param batt 电池电压（单位：伏特）
 * @param temp 温度（单位：摄氏度）
 * @param humi 湿度（单位：百分比）
 * @param ec 电导率（单位：mS/cm 或其他单位）
 * @note 使用 u8g2 库绘制界面，右上角显示电池图标和电压，中间显示温度、湿度、EC 数据
 */
void Display_MainScreen(float batt, float temp, float humi, float ec)
{
  u8g2_ClearBuffer(&u8g2);
  u8g2_SetFont(&u8g2, u8g2_font_6x12_tr); // 字体选择

  char buf[32];

  // 右上角电池
  sprintf(buf, "%.2fV", batt);
  int w = u8g2_GetStrWidth(&u8g2, buf);
  u8g2_DrawStr(&u8g2, 128 - w, 10, buf);

  // 电池图标框
  u8g2_DrawFrame(&u8g2, 128 - 20, 0, 16, 8);   // 电池外框
  int level = (int)(batt / 4.2 * 14);          // 电量转格子宽度（假设4.2V满电）
  if (level > 14) level = 14;
  u8g2_DrawBox(&u8g2, 128 - 20 + 1, 1, level, 6); // 填充电量

  // 中间数据显示
  sprintf(buf, "Temp: %.1fC", temp);
  u8g2_DrawStr(&u8g2, 0, 28, buf);

  sprintf(buf, "Humi: %.1f%%", humi);
  u8g2_DrawStr(&u8g2, 0, 44, buf);

  sprintf(buf, "EC: %.2f", ec);
  u8g2_DrawStr(&u8g2, 0, 60, buf);

  u8g2_SendBuffer(&u8g2);
}


/**
 * @brief 显示开机动画，包含设备名称和进度条
 * @author mdqr
 * @note 动画循环 100 次，逐步填充进度条，每次循环延时 30ms，总计约 3 秒
 *       显示内容包括设备名称、进度条和百分比
 */
void Display_BootAnimation(void)
{
  for (int i = 0; i <= 100; i++) {
    u8g2_ClearBuffer(&u8g2);
    u8g2_SetFont(&u8g2, u8g2_font_ncenB08_tr);
    u8g2_DrawStr(&u8g2, 30, 20, "My Device");

    // 进度条
    int pb_x = 14, pb_y = 40, pb_w = 100, pb_h = 8;
    int pb_fill = i * pb_w / 100;
    u8g2_DrawFrame(&u8g2, pb_x, pb_y, pb_w, pb_h);
    u8g2_DrawBox(&u8g2, pb_x + 1, pb_y + 1, pb_fill - 2 > 0 ? pb_fill - 2 : 0, pb_h - 2);

    char buf[8];
    sprintf(buf, "%d%%", i);
    u8g2_DrawStr(&u8g2, pb_x + pb_w + 5, pb_y + pb_h, buf);

    u8g2_SendBuffer(&u8g2);
    HAL_Delay(30); // 动画延时
  }
}


/**
 * @brief 显示线性进度条动画，模拟 3 秒加载过程
 * @author mdqr
 * @param elapsed_ms 已逝时间（单位：毫秒），用于计算进度百分比
 * @note
 *       - 使用 u8g2 库在 128x64 像素的 OLED 屏幕上绘制进度条
 *       - 进度条从左到右填充，3 秒内从 0% 到 100%
 *       - 显示“Measuring...”提示文本，进度条位于屏幕中间
 *       - 适合表示设备初始化或数据测量中的等待过程
 */
void Display_LoadingBar(uint32_t elapsed_ms) {
  int bar_x = 20, bar_y = 30, bar_w = 88, bar_h = 10;
  int progress = (elapsed_ms * 100) / 2500; // 显示3秒的进度条
  if (progress > 100) progress = 100;

  u8g2_ClearBuffer(&u8g2);
  u8g2_SetFont(&u8g2, u8g2_font_6x12_tr);
  u8g2_DrawStr(&u8g2, 40, 20, "Measuring...");

  // 外框
  u8g2_DrawFrame(&u8g2, bar_x, bar_y, bar_w, bar_h);

  // 内部进度
  int fill = bar_w * progress / 100;
  u8g2_DrawBox(&u8g2, bar_x+1, bar_y+1, fill-2, bar_h-2);

  u8g2_SendBuffer(&u8g2);
}
/**
 * @brief 调用示例：运行 3 秒进度条动画
 * @author mdqr
 * @note
 *       - 使用 HAL_GetTick() 获取当前时间，计算已逝时间
 *       - 每 100ms 更新一次进度条，保持动画流畅
 *       - 需在 FreeRTOS 任务或主循环中调用
 * @example
 *   uint32_t start = HAL_GetTick();
 *   while (HAL_GetTick() - start < 3000) {
 *       uint32_t elapsed = HAL_GetTick() - start;
 *       Display_LoadingBar(elapsed);
 *       osDelay(100);
 *   }
 * @effect 进度条从左到右线性填充，3 秒内完成，配合“Measuring...”提示
 */



/**
 * @brief 显示波浪线动画，模拟数据流动或处理中的动态效果
 * @author mdqr
 * @param t 已逝时间（单位：毫秒），用于控制波浪线的移动
 * @note
 *       - 使用 u8g2 库在 128x64 像素的 OLED 屏幕上绘制动态波浪线
 *       - 波浪线基于正弦函数生成，水平滚动，营造“数据流动”效果
 *       - 显示“Measuring...”提示，波浪线位于屏幕中间
 *       - 适合表示传感器数据采集或后台处理中的动态状态
 */
void Display_WaveAnimation(uint32_t t) {
  u8g2_ClearBuffer(&u8g2);
  u8g2_SetFont(&u8g2, u8g2_font_6x12_tr);
  u8g2_DrawStr(&u8g2, 40, 10, "Measuring...");

  int mid = 32;
  for (int x = 0; x < 128; x++) {
    int y = mid + (int)(10 * sinf((x + t/50.0f) * 0.2f));
    u8g2_DrawPixel(&u8g2, x, y);
  }

  u8g2_SendBuffer(&u8g2);
}
/**
 * @brief 调用示例：运行 3 秒波浪线动画
 * @author mdqr
 * @note
 *       - 使用 HAL_GetTick() 获取当前时间，传递已逝时间 t
 *       - 每 50ms 更新一次波浪线，保持动画流畅
 *       - 需在 FreeRTOS 任务或主循环中调用
 * @example
 *   uint32_t start = HAL_GetTick();
 *   while (HAL_GetTick() - start < 3000) {
 *       Display_WaveAnimation(HAL_GetTick() - start);
 *       osDelay(50);
 *   }
 * @effect 一条波浪线在屏幕中间左右滚动，配合“Measuring...”提示，持续 3 秒
 */


/**
 * @brief 显示小球旋转加载动画，模拟等待或测量过程
 * @author mdqr
 * @note
 *       - 使用 u8g2 库在 128x64 像素的 OLED 屏幕上绘制动态小球动画
 *       - 小球沿圆形轨迹旋转，配合“Measuring...”提示，营造“正在加载”效果
 *       - 动画通过静态变量 angle 控制小球位置，每次旋转 15°，360°后归零
 *       - 适合表示设备初始化、数据采集或处理中的等待状态
 *       - 需在 FreeRTOS 任务中循环调用，建议每 100ms 刷新一次，持续 3 秒
 */
void Display_LoadingAnimation(void) {
  // 静态变量 angle 记录小球当前角度（度），在多次调用间保持状态
  static int angle = 0;

  // 定义圆形轨迹的参数：圆心 (x=64, y=32) 在屏幕中心，半径 20 像素
  int cx = 64, cy = 32, r = 20;

  // 清空 u8g2 缓冲区，准备绘制当前动画帧
  u8g2_ClearBuffer(&u8g2);

  // 设置字体为 6x12 像素，适合小型 OLED 显示屏
  u8g2_SetFont(&u8g2, u8g2_font_6x12_tr);

  // 在屏幕底部 (x=40, y=60) 显示“Measuring...”提示，表示正在测量或加载
  u8g2_DrawStr(&u8g2, 40, 60, "Measuring...");

  // 绘制圆形轨迹（完整圆，半径 20 像素，位于屏幕中心）
  u8g2_DrawCircle(&u8g2, cx, cy, r, U8G2_DRAW_ALL);

  // 计算小球在圆上的坐标
  // 使用 cosf 和 sinf 计算 x, y，角度转换为弧度 (angle * π / 180)
  int x = cx + (int)(r * cosf(angle * 3.14159 / 180));
  int y = cy + (int)(r * sinf(angle * 3.14159 / 180));

  // 绘制小球（实心圆盘，半径 3 像素，位于计算出的 x, y 坐标）
  u8g2_DrawDisc(&u8g2, x, y, 3, U8G2_DRAW_ALL);

  // 将缓冲区内容发送到 OLED 屏幕，显示当前帧
  u8g2_SendBuffer(&u8g2);

  // 每次调用增加 15°，控制小球旋转速度
  angle += 15;

  // 当角度达到 360° 时归零，保持动画循环
  if (angle >= 360) angle = 0;
}

/**
 * @brief 调用示例：运行 3 秒小球旋转动画
 * @author mdqr
 * @note
 *       - 使用 HAL_GetTick() 获取当前时间，控制动画持续 3 秒
 *       - 每 100ms 调用一次 Display_LoadingAnimation，保持动画流畅
 *       - 需在 FreeRTOS 任务或主循环中调用
 * @example
 *   uint32_t start = HAL_GetTick();
 *   while (HAL_GetTick() - start < 3000) {
 *       Display_LoadingAnimation();
 *       osDelay(100);
 *   }
 * @effect 小球沿圆形轨迹旋转，配合“Measuring...”提示，持续 3 秒
 */